/** A sector tile.
*/
function SectorTile()
{
	this.name = "Unknown";
	this.character = " ";
	this.fg = Color.white;
	this.bg = Color.black;
	this.blockWalk = false;
	this.blockVis = false;
}
SectorTile.prototype =
{
};

/** A sector map is a map on which gameplay occurs.

Sector maps are generated en-masse at world generation time to establish the
characteristics of the map but feature generation is left until load time.
@param name The name of this map.
@param type The type string for this map.
@param seed The Rrng seed for this map.
@param id Id number of the map. This corrosponds to the world map sectors.
@param level The level of the mpa, starting at 1.
*/
function SectorMap(name, type, seed, id, level)
{
	this.buffer = host.screen;
	this.xOfs = 0;
	this.yOfs = 0;
	this.width = 40;
	this.height = 32;
	this.name = name;
	this.type = type;
	this.seed = seed;
	this.id = id;
	this.level = level;
	this.hasBeenGenerated = false;
	this.heat = 0;
	this.moisture = 0;
	this._tiles = [];
	this._ents = [];
	this._blockWalkMap = [];
	this._blockVisMap = [];
	this._entCountMap = [];
	this._visSet = [];
	this._lastVisSet = [];
	this._knownMap = [];
	this._defaultX = 0;
	this._defaultY = 10;
	this._player = null;
}
SectorMap._registeredMaps = {};
SectorMap._saveData = {};
/** Add a map to the pool of managed maps.
@param map Map to add.
*/
SectorMap.registerMap = function(map)
{
	var token = map.id + "_" + map.level;
	if(SectorMap._registeredMaps[token] != undefined)
		throw new Error("Duplicate sector map registered " + token);
	SectorMap._registeredMaps[token] = map;
}
/** Get the save data for a sector map.
 *@param map The map to get save data for.
 */
SectorMap.getSaveFor = function(map)
{
	var token = map.id + "_" + map.level;
	return SectorMap._saveData[token];
}
/** Generate save data for all registered maps.
*/
SectorMap.getSaveData = function()
{
	var ret = {};
	for(var o in SectorMap._registeredMaps)
	{
		if(!SectorMap._registeredMaps.hasOwnProperty(o))
			continue;
		var map = SectorMap._registeredMaps[o];
		var token = map.id + "_" + map.level;
		if(map.hasBeenGenerated)
		{
			var mapData = [];
			map.serialize(mapData);
			ret[token] = mapData;
		}
		else if(SectorMap._saveData.hasOwnProperty(token))
			ret[token] = SectorMap._saveData[token];
	}
	return ret;
}
/** Clear all sector maps.
 *
 *Do this when loading a game or starting a new game.
 */
SectorMap.unregisterAllMaps = function()
{
	SectorMap._registeredMaps = {};
}
/** Load sector map data from a save file.
 *@param data Save data.
 */
SectorMap.loadSaveData = function(data)
{
	SectorMap._saveData = data;
}
SectorMap.pointOfInterestTypes =
[
	"cave",
	"zombietown",
	"base"
];
SectorMap.prototype =
{
	serialize: function(s)
	{
		var i;

		// Serialize all entities backwards
		for(i = this._ents.length - 1; i >= 0 ; --i)
		{
			var ent = this._ents[i];

			// Skip the player's entity
			if(ent == player)
				continue;
			
			s.push(Entity.getSerializedForm(ent));
		}

		// Save visibility information
		var visRle = [];
		var lastValue = 0;
		var count = 0;
		for(i = 0; i < this._knownMap.length; ++i)
		{
			if(this._knownMap[i] == lastValue)
				++count;
			else
			{
				visRle.push(count);
				lastValue = this._knownMap[i];
				count = 1;
			}
		}
		visRle.push(count);
		s.push(visRle);
	},
	deserialize: function(s)
	{
		// Load visibility information.
		var visRle = s.pop();
		var i, j, visible = false, ofs = 0;
		for(i = 0; i < visRle.length; ++i)
		{
			for(j = 0; j < visRle[i]; ++j)
			{
				this._knownMap[ofs++] = visible ? 1 : 0;
			}
			visible = !visible;
		}
		
		// Deserialize all entities
		this._ents = [];
		while(true)
		{
			var data = s.pop();
			if(!data)
				break;
			this._ents.push(Entity.fromSerializedData(data));
		}
	},
	/** Generate the features of the map including mobiles and objects.
	*/
	generate: function()
	{
		if(!this.hasBeenGenerated)
		{
			this.hasBeenGenerated = true;

			var rng = new Rrng(this.seed);
			var self = this, i;

			// Create all the tiles and vis set data points
			this._tiles = [];
			for(i = 0; i < this.width * this.height; ++i)
			{
				this._tiles.push(new SectorTile());
				this._visSet.push(0);
				this._lastVisSet.push(0);
				this._knownMap.push(0);
			}

			// Start by laying down the base colors if we have heat and moisture values.
			if(this.heat > 0)
			{
				var mf = Math.min(Math.max(this.moisture, 0), 0.33);
				var baseColor = new Color(
					0.66 * this.heat,
					(0.33 + (0.33 - mf)) * this.heat,
					0
				);
				for(i = 0; i < this.width * this.height; ++i)
				{
					this._tiles[i].bg = new Color(
						baseColor.r + rng.nextFloat() * 0.1 - 0.05,
						baseColor.g + rng.nextFloat() * 0.025 - 0.02,
						baseColor.b
					);
					this._tiles[i].name = "desert floor";
				}
			}

			// DEBUG Random rats, yay!
			for(i = 0; i < 20; ++i)
			{
				var dx = rng.next(0, this.width);
				var dy = rng.next(0, this.height);
				var door = new RatMobile();
				door.x = dx;
				door.y = dy;
				this._ents.push(door);
			}

			// Medical pack
			var medPack = new MedPackItem();
			medPack.x = 4;
			medPack.y = 16;
			this._ents.push(medPack);

			// TODO Call the type-specific map generator

			// Try to load the entities from saved data if present.
			var save = SectorMap.getSaveFor(this);
			if(save)
				this.deserialize(save);

			// Build the base flag maps and set entity parents
			for(i = 0; i < this.width * this.height; ++i)
			{
				var tile = this._tiles[i];
				this._blockWalkMap.push(tile.blockWalk ? 1 : 0);
				this._blockVisMap.push(tile.blockVis ? 1 : 0);
				this._entCountMap.push(0);
			}
			for(i = 0; i < this._ents.length; ++i)
			{
				var ent = this._ents[i];
				if(ent.x < 0 ||
					ent.y < 0 ||
					ent.x >= this.width ||
					ent.y >= this.height)
					continue;
				var idx = ent.y * this.width + ent.x;
				++this._entCountMap[idx];
				if(ent.blockWalk)
					++this._blockWalkMap[idx];
				if(ent.blockVis)
					++this._blockVisMap[idx];
				ent.parent = this;
				ent.onArriveAtPosition(true);
			}
		}
		// Re-initialize entities
		for(i = 0; i < this._ents.length; ++i)
		{
			var ent = this._ents[i];
			if(ent.isMobile)
				ent.createController();
		}
	},
	/** Called to do a turn for all entities in the map.
	*/
	doTurn: function()
	{
		var updateList = [];
		for(var i = 0; i < this._ents.length; ++i)
		{
			updateList.push(this._ents[i]);
		}
		for(var i = 0; i < updateList.length; ++i)
		{
			var ent = updateList[i];
			if(!ent.isMobile ||
				ent == player)
				continue;
			ent.time += player.speed;
			while(ent.time > 0)
			{
				ent.doTurn();
				ent.time -= ent.speed;
			}
		}
	},
	/** Adds the player's entity to the map.
	@param player The player's entity.
	@param fromWorldMap True if the player is comming from the world map.
	*/
	addPlayer: function(player, fromWorldMap)
	{
		if(fromWorldMap)
		{
			player.x = this._defaultX;
			player.y = this._defaultY;
		}
		this._player = player;
		this.addEntity(player);
	},
	/** Add an entity to the map.
	@param ent Entity to add.
	*/
	addEntity: function(ent)
	{
		this._ents.push(ent);
		ent.parent = this;
		this._onArrive(ent);
		this.renderPosition(ent.x, ent.y);
	},
	/** Remove an entity from the map.
	@param ent Entity to remove.
	*/
	removeEntity: function(ent)
	{
		this._onLeave(ent);
		ent.parent = null;
		var idx = this._ents.indexOf(ent);
		if(idx < 0)
			return;
		this._ents.splice(idx, 1);
		this.renderPosition(ent.x, ent.y);
	},
	/** Try to move an entity.
	@param ent Entity to move.
	@param x New X location.
	@param y New Y location.
	@returns true if successful, false otherwise.
	*/
	tryMoveEntity: function(ent, x, y)
	{
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return false;
		if(this._blockWalkMap[y * this.width + x] > 0)
			return false;
		
		this.removeEntity(ent);
		var lastX = ent.x, lastY = ent.y;
		ent.x = x;
		ent.y = y;
		this.addEntity(ent);
		this.renderPosition(lastX, lastY);
		return true;
	},
	/** Return the tile at a position or null if out of bounds.
	@param x X position.
	@param y Y position.
	 */
	getTile: function(x, y)
	{
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return null;
		return this._tiles[y * this.width + x];
	},
	/** Return an array of all of the entities at a position.
	@param x X parameter.
	@param y Y parameter.
	*/
	getEntitiesAtPosition: function(x, y)
	{
		var ret = [], i, ent;
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return ret;
		if(this._entCountMap[y * this.width + x] > 0)
		{
			for(i = 0; i < this._ents.length; ++i)
			{
				ent = this._ents[i];
				if(ent.x != x ||
					ent.y != y)
					continue;
				ret.push(ent);
			}
		}
		return ret;
	},
	/** Returns a description of the position.
	@param x X position.
	@param y Y position.
	*/
	getPositionDescription: function(x, y)
	{
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return "Unknown";

		var i = y * this.width + x;
		if(this._knownMap[i] == 0)
			return "Unknown";
		var desc = this._tiles[i].name;
		if(this._visSet[i] == 0)
			return desc;
		if(this._entCountMap[i] > 0)
		{
			var foundCount = 0;
			for(i = this._ents.length - 1; i >= 0; --i)
			{
				var ent = this._ents[i];
				if(ent.x != x ||
					ent.y != y)
					continue;
				if(foundCount == 0)
					desc += ": ";
				else
					desc += ", ";
				++foundCount;
				desc += ent.name;
			}
		}
		return desc;
	},
	/** Called when an entity arrives at a position.
	@param ent The entity.
	*/
	_onArrive: function(ent)
	{
		if(ent.x < 0 ||
			ent.y < 0 ||
			ent.x >= this.width ||
			ent.y >= this.height)
			return;
		var i = ent.y * this.width + ent.x;
		if(ent.blockWalk)
			++this._blockWalkMap[i];
		if(ent.blockVis)
			++this._blockVisMap[i];
		++this._entCountMap[i];
		ent.onArriveAtPosition(false);
		if(ent == player)
		{
			this._updateVisibilitySetFrom(ent);
			this._applyVisibilitySet();
		}
	},
	/** Called when an entity leaves a position.
	@param ent The entity.
	*/
	_onLeave: function(ent)
	{
		if(ent.x < 0 ||
			ent.y < 0 ||
			ent.x >= this.width ||
			ent.y >= this.height)
			return;
		var i = ent.y * this.width + ent.x;
		if(ent.blockWalk)
			--this._blockWalkMap[i];
		if(ent.blockVis)
			--this._blockVisMap[i];
		--this._entCountMap[i];
		ent.onLeavePosition();
	},
	/** Determines if point b is visible from point a.
	 *@param ax X position of point a.
	 *@param ay Y position of point a.
	 *@param bx X position of point b.
	 *@param by Y position of point b.
	 *@param range Optional range for the test.
	 */
	isVisibleFrom: function(ax, ay, bx, by, range)
	{
		var dx = ax - bx;
		var dy = ay - by;
		if(range &&
			dx * dx + dy * dy > range * range)
			return false;
		var absdx = dx;
		var absdy = dy;
		if(absdx < 0) absdx *= -1;
		if(absdy < 0) absdy *= -1;

		// Line driver, based on Bresenham's line algorithm
		// If the line is steeper that 45% reflect the line
		var steep = absdy > absdx;
		var x0, x1, y0, y1;
		if(steep)
		{
			x0 = ay;
			x1 = by;
			y0 = ax;
			y1 = bx;
		}
		else
		{
			x0 = ax;
			x1 = bx;
			y0 = ay;
			y1 = by;
		}
		dx = x1 - x0;
		dy = y1 - y0;
		absdx = dx;
		absdy = dy;
		if(absdx < 0) absdx *= -1;
		if(absdy < 0) absdy *= -1;
		var error = absdx / 2;
		var stepy = 1;
		if(y0 > y1) stepy = -1;
		var stepx = 1;
		if(x0 > x1) stepx = -1;
		var realx, realy;
		var liney = y0;
		var linex = x0;
		var steps = x1 - x0;
		if(steps < 0) steps *= -1;
		// NOTE: This for loop omitts the first and last positions
		for(var iStep = 1; iStep < steps; ++iStep)
		{
			if(steep)
			{
				realx = liney;
				realy = linex;
			}
			else
			{
				realx = linex;
				realy = liney;
			}
			error -= absdy;
			if(error < 0)
			{
				liney += stepy;
				error += absdx;
			}

			// Out of bounds tests always return false.
			if(realx < 0 ||
				realy < 0 ||
				realx >= this.width ||
				realy >= this.height)
				return false;

			// At this point, realx and realy are our absolute map coordinates along this line.
			// If this map position blocks visibility we return false;
			if(this._blockVisMap[realy * this.width + realx] > 0)
				return false;
		}

		// If we get here without returning false we have a line of sight.
		return true;
	},
	/** Updates the visibility set data based on an entity.
	 *
	 * Visibility method that uses a line-of-sight test for every map position within the visible
	 * range. Positions that were marked as visible in previous line-of-sight tests will be skipped.
	 * A post-processing step is then applied to clean up the most common artifacts caused by this
	 * type of visibility method.
	 *
	 * This is a useful visibility method to use for very ranged-combat intensive games as any
	 * visibile mobile will nessecarily have line-of-sight to and from the player (in most cases).
	 *
	 * The data format for the visibility buffer is as follows:
	 * 0	Position is not visible
	 * 1	Position was marked visible as a result of the post-processing phase
	 * 2	Position was marked visible as the point of obstruction of a line of sight
	 * 4	Position was marked visible as it is within line of sight
	 *
	 * This is significant only to the post-processing phase, which uses sums of neighbor cell
	 * visibility to determine if a cell should be marked visible.
	 *
	 * Here's how the post processing works:
	 *
	 * We look at every position in our visibility range, plus a one-cell periffery. If a position's
	 * TILE blocks visibility AND it is not already marked visibile then we do this:
	 *
	 * Add up the visibility bytes from all eight adjacent cells who's tiles. If any one of these tiles
	 * has a value of 4 we mark our position as visible too. This gets the "looking down a wall"
	 * and "interior corner" edge cases which cause most of the artifacting with basic ray casting. This
	 * does create a little bit of bleeding though.
	 *
	 * This visibility method was ported from an unreleased C# project I did about a year prior and is
	 * not 100% optimized for JavaScript. As it is it is not suitable for large maps. The origional C#
	 * implementation used a helper class that allowed this.
	 *@param ent The entity to use.
	 */
	_updateVisibilitySetFrom: function(ent)
	{
		var i;

		// Swap vis sets
		for(i = 0; i < this._visSet.length; ++i)
		{
			this._lastVisSet[i] = this._visSet[i];
			this._visSet[i] = 0;
		}

		// Data setup
		var mobVisRangeSquared = (ent.visibilityRange - 0.5) * (ent.visibilityRange - 0.5);
		var mx = ent.x;
		var my = ent.y;

		// Spiral driver
		var spMinX = mx - ent.visibilityRange;
		var spMinY = my - ent.visibilityRange;
		var spMaxX = mx + ent.visibilityRange;
		var spMaxY = my + ent.visibilityRange;
		var ix = spMinX;
		var iy = spMinY;
		var spStepX = 1;
		var spStepY = 0;
		while(true)
		{
			// End of spiral, break
			if(spMinX == spMaxX ||
				spMinY == spMaxY)
				  break;

			// Right extent
			if(spStepX == 1 &&
				ix >= spMaxX - 1)
			{
				spStepX = 0;
				spStepY = 1;
				spMinY++;
			}
			// Left extent
			if(spStepX == -1 &&
				ix <= spMinX)
			{
				spStepX = 0;
				spStepY = -1;
				spMaxY--;
			}
			// Lower extent
			if(spStepY == 1 &&
				iy >= spMaxY - 1)
			{
				spStepY = 0;
				spStepX = -1;
				spMaxX--;
			}
			// Upper extent
			if(spStepY == -1 &&
				iy <= spMinY)
			{
				spStepY = 0;
				spStepX = 1;
				spMinX++;
			}

			// Process position. We do this in a do ... while(false) so we can use break
			// to fall through to the next spiral loop with break.
			do
			{
				// Out of bounds short-circut
				if(ix < 0 ||
					iy < 0 ||
					ix >= this.width ||
					iy >= this.height)
					break;

				// Already set to visible short-circut
				if(this._visSet[iy * this.width + ix] > 0)
					break;

				// Out-of-Range Short Circut
				var dx = ix - mx;
				var dy = iy - my;
				if(dx * dx + dy * dy > mobVisRangeSquared)
					break;
				var absdx = dx;
				var absdy = dy;
				if(absdx < 0) absdx *= -1;
				if(absdy < 0) absdy *= -1;

				// Line driver, based on Bresenham's line algorithm
				// If the line is steeper that 45% reflect the line
				var steep = absdy > absdx;
				var x0, x1, y0, y1;
				if(steep)
				{
					x0 = my;
					x1 = iy;
					y0 = mx;
					y1 = ix;
				}
				else
				{
					x0 = mx;
					x1 = ix;
					y0 = my;
					y1 = iy;
				}
				dx = x1 - x0;
				dy = y1 - y0;
				absdx = dx;
				absdy = dy;
				if(absdx < 0) absdx *= -1;
				if(absdy < 0) absdy *= -1;
				var error = absdx / 2;
				var stepy = 1;
				if(y0 > y1) stepy = -1;
				var stepx = 1;
				if(x0 > x1) stepx = -1;
				var realx, realy;
				var liney = y0;
				var linex = x0;
				var steps = x1 - x0;
				if(steps < 0) steps *= -1;
				for(var iStep = 0; iStep <= steps; ++iStep)
				{
					if(steep)
					{
						realx = liney;
						realy = linex;
					}
					else
					{
						realx = linex;
						realy = liney;
					}
					error -= absdy;
					if(error < 0)
					{
						liney += stepy;
						error += absdx;
					}

					// At this point, realx and realy are our absolute map coordinates along this line.
					// If this map position blocks visibility we set it to blocking visible and bail
					// out of the line driver.
					if(realx >= 0 &&
						realy >= 0 &&
						realx < this.width &&
						realy < this.height)
					{
						if(this._blockVisMap[realy * this.width + realx] > 0)
						{
							this._visSet[realy * this.width + realx] = 2;
							break;
						}
						// Otherwise we have a map position that is visible from the reference mobile.
						this._visSet[realy * this.width + realx] = 4;
					}
					linex += stepx;
				}
			} while(false);

			// Step position
			ix += spStepX;
			iy += spStepY;
		}

		// Postprocessing, see method remarks for logic
		spMinX = mx - ent.visibilityRange;
		spMinY = my - ent.visibilityRange;
		spMaxX = mx + ent.visibilityRange;
		spMaxY = my + ent.visibilityRange;
		for(iy = spMinY - 1; iy < spMaxY + 1; ++iy)
		{
			for(ix = spMinX - 1; ix < spMaxX + 1; ++ix)
			{
				// Out of bounds short-circut
				if(ix < 0 ||
					iy < 0 ||
					ix >= this.width ||
					iy >= this.height)
					break;

				var tile = this._tiles[iy * this.width + ix];
				if(!tile.blockVis ||
					this._visSet[iy * this.width + ix] > 0)
					continue;

				// Inspect West
				if(ix > 0)
				{
					// W
					if(this._visSet[(iy) * this.width + (ix - 1)] >= 4)
					{
						this._visSet[(iy) * this.width + (ix)] = 1;
						continue;
					}
					// NW
					if(iy > 0 &&
						this._visSet[(iy - 1) * this.width + (ix - 1)] >= 4)
					{
						this._visSet[(iy) * this.width + (ix)] = 1;
						continue;
					}
					// SW
					if(iy < this.height - 1 &&
						this._visSet[(iy + 1) * this.width + (ix - 1)] >= 4)
					{
						this._visSet[(iy) * this.width + (ix)] = 1;
						continue;
					}
				}
				// Inspect East
				if(ix < this.width - 1)
				{
					// E
					if(this._visSet[(iy) * this.width + (ix + 1)] >= 4)
					{
						this._visSet[(iy) * this.width + (ix)] = 1;
						continue;
					}
					// NE
					if(iy > 0 &&
						this._visSet[(iy - 1) * this.width + (ix + 1)] >= 4)
					{
						this._visSet[(iy) * this.width + (ix)] = 1;
						continue;
					}
					// SE
					if(iy < this.height - 1 &&
						this._visSet[(iy + 1) * this.width + (ix + 1)] >= 4)
					{
						this._visSet[(iy) * this.width + (ix)] = 1;
						continue;
					}
				}
				// Inspect North
				if(iy > 0 &&
					this._visSet[(iy - 1) * this.width + (ix)] >= 4)
				{
					this._visSet[(iy) * this.width + (ix)] = 1;
					continue;
				}
				// Inspect South
				if(iy < this.height - 1 &&
					this._visSet[(iy + 1) * this.width + (ix)] >= 4)
				{
					this._visSet[(iy) * this.width + (ix)] = 1;
					continue;
				}
			}
		}
	},
	/** Analyses the last visibility set and the current visibility set and
	 *takes appropriote action such as marking tiles as known and redrawing
	 *positions that have changed.
	 */
	_applyVisibilitySet: function()
	{
		var ix, iy, ofs = 0;
		for(iy = 0; iy < this.height; ++iy)
		{
			for(ix = 0; ix < this.width; ++ix)
			{
				if(this._visSet[ofs] > 0)
					this._knownMap[ofs] = 1;
				if((this._lastVisSet[ofs] > 0) !=
					(this._visSet[ofs] > 0))
					this.renderPosition(ix, iy);
				++ofs;
			}
		}
	},
	/** Render a map location.
	@param x X location.
	@param y Y location.
	*/
	renderPosition: function(x, y)
	{
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return;
		// Visibility check
		var ofs = y * this.width + x;
		var tile = this._tiles[ofs];
		if(this._visSet[ofs] > 0)
		{
			this.buffer.setCell(x + this.xOfs, y + this.yOfs, tile.character, tile.fg, tile.bg);
			if(this._entCountMap[ofs] > 0)
			{
				for(var i = 0; i < this._ents.length; ++i)
				{
					ent = this._ents[i];
					if(ent.x != x ||
						ent.y != y)
						continue;
					this.buffer.setCell(ent.x + this.xOfs, ent.y + this.yOfs, ent.character, ent.fg, ent.bg);
				}
			}
		}
		else if(this._knownMap[ofs] > 0)
		{
			this.buffer.setCell(x + this.xOfs, y + this.yOfs, tile.character, tile.fg.newScaled(0.5), tile.bg.newScaled(0.5));
		}
		else
		{
			this.buffer.setCell(x + this.xOfs, y + this.yOfs, " ", null, Color.black);
		}
	},
	/** Render the entire map.
	*/
	render: function()
	{
		var ix, iy, i, tile, ent, ofs;
		for(iy = 0; iy < this.height; ++iy)
		{
			for(ix = 0; ix < this.width; ++ix)
			{
				ofs = iy * this.width + ix;
				tile = this._tiles[ofs];
				if(this._visSet[ofs] > 0)
				{
					this.buffer.setCell(ix + this.xOfs, iy + this.yOfs, tile.character, tile.fg, tile.bg);
				}
				else if(this._knownMap[ofs] > 0)
				{
					this.buffer.setCell(ix + this.xOfs, iy + this.yOfs, tile.character, tile.fg.newScaled(0.5), tile.bg.newScaled(0.5));
				}
				else
				{
					this.buffer.setCell(ix + this.xOfs, iy + this.yOfs, " ", null, Color.black);
				}
			}
		}
		for(i = 0; i < this._ents.length; ++i)
		{
			ent = this._ents[i];
			if(this._visSet[ent.y * this.width + ent.x] > 0)
				this.buffer.setCell(ent.x + this.xOfs, ent.y + this.yOfs, ent.character, ent.fg, ent.bg);
		}
	},
	/** Draw an overlay on the map
	@param x X position.
	@param y Y position.
	@param c Character.
	@param fg Foreground color.
	@param bg Background color.
	*/
	renderOverlay: function(x, y, c, fg, bg)
	{
		if(x < 0 ||
			y < 0 ||
			x >= this.width ||
			y >= this.height)
			return;
		this.buffer.setCell(x + this.xOfs, y + this.yOfs, c, fg, bg);
	}
};
